package com.myfum.mobile.tetris.data
{
	import com.myfum.mobile.tetris.Config;
	import com.myfum.mobile.tetris.data.cubes.CubeData;
	import com.myfum.mobile.tetris.data.cubes.EmptyCubeData;
	import com.myfum.mobile.tetris.data.cubes.StandingCubeData;
	import com.myfum.mobile.tetris.data.cubes.UnbreakableCubeData;
	import com.myfum.mobile.tetris.events.ClearedLineEvent;
	import com.myfum.mobile.tetris.events.ControlEvent;
	import com.myfum.mobile.tetris.events.MapEvent;
	import com.myfum.mobile.tetris.events.P2PEvent;
	import com.myfum.mobile.tetris.interfaces.IShape;
	import com.myfum.mobile.tetris.utils.InteractionManager;
	
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.geom.Point;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;
	
	import org.robotlegs.mvcs.Actor;
	
	public class TetrisMap extends Actor
	{
		private var map:Dictionary;
		private var shape:IShape;
		private var timer:Sprite;
		private var counter:int = 0;
		private var startTime:int;
		
		public function TetrisMap()
		{
			super();
			timer = new Sprite();
			resetMap();
		}
		
		public function appendShape(shape:IShape):void{
			this.shape = shape;
			countdown();
			redraw(null);
			dispatch(new MapEvent(MapEvent.MAP_APPEND, null, this.shape));
		}
		
		public function getCubeDataByPoint(p:Point):CubeData{
			return map[p.toString()] as CubeData;
		}
		
		public function moveLeft():void{
			if(shape == null){
				return;
			}
			if(validateLeft()){
				redraw(shape.stepLeft);
			}
		}
		
		public function moveRight():void{
			if(shape == null){
				return;
			}
			if(validateRight()){
				redraw(shape.stepRight);
			}
		}
		
		public function moveDown():void{
			if(shape == null){
				return;
			}
			if(validateDown()){
				redraw(shape.stepDown);
				reset();
			}
		}
		
		public function drop():void{
			var was:IShape = shape.clone() as IShape;
			while(validateDown()){
				shape.stepDown();
			}
			stopCountdown();
			dispatch(new MapEvent(MapEvent.MAP_CHANGE, was, shape));
			updateMap();
		}
		
		public function rotate():void{
			if(shape == null){
				return;
			}
			var was:IShape = shape.clone() as IShape;
			shape.rotate();
			if(!preValidate()){
				shape.undoRotate();
				return;
			}
			dispatch(new MapEvent(MapEvent.MAP_CHANGE, was, shape));
		}
		
		public function cleanUpLine():void{
			var hasEmptyCube:Boolean = false;
			var p:Point = new Point();
			var c:CubeData;
			var clearedLines:Vector.<int> = new Vector.<int>();
			for(var i:int = Config.STAGE_HEIGHT-1;i > 0;i--){
				hasEmptyCube = false;
				for(var j:int = 0;j < Config.STAGE_WIDTH;j++){
					p.x = j;
					p.y = i;
					c = map[p.toString()];
					if(c is UnbreakableCubeData){
						//this line is a unbreakable line
						break;
					}
					if(c is EmptyCubeData){
						hasEmptyCube = true;
						break;
					}
				}
				if(!hasEmptyCube){
					//clean up this line
					cleanLine(i);
					i = Math.min(Config.STAGE_HEIGHT, i+1);
					clearedLines.push(i);
				}
			}
			if(clearedLines.length > 0){
				dispatch(new MapEvent(MapEvent.REDRAW_MAP, null, null));
				dispatch(new ClearedLineEvent(ClearedLineEvent.LINE_CLEARED, clearedLines));
			}
		}
		
		private function getDataToSend():Object{
			var s:Array;
			if(shape){
				s = [];
				for each(var c:CubeData in shape.cubes){
					s.push(c.x + '-' + c.y);
				}
			}
			var m:Array = [];
			for(var i:int = 0;i<Config.STAGE_HEIGHT*Config.STAGE_WIDTH;i++){
				var p:Point = new Point(i%10, Math.floor(i/10));
				m.push(map[p.toString()].type.toString());
			}
			var o:Object = {};
			o.map = m.join("");
			o.shape = null;
			if(s){
				o.shape = s.join(',');
			}
			return o;
		}
		
		private function cleanLine(y:int):void{
			var p:Point = new Point(0, y);
			var p1:Point = new Point();
			var i:int;
			/*for(i = 0;i<Config.STAGE_WIDTH;i++){
				p.x = i;
				map[p.toString()] = new EmptyCubeData(p.x, p.y, Config.empty_cube);
			}*/
			//move everything above down for 1 (except empty cube)
			for(var j:int = y;j > 0;j--){
				for(i = 0;i<Config.STAGE_WIDTH;i++){
					p.x = i;
					p.y = j;
					p1.x = p.x;
					p1.y = p.y - 1;
					map[p.toString()] = map[p1.toString()];
					map[p.toString()].y = Math.min(Config.STAGE_HEIGHT - 1, map[p.toString()].y + 1);
				}
			}
			//fill the first line with empty cubes
			p.y = 0;
			for(i = 0;i<Config.STAGE_WIDTH;i++){
				p.x = i;
				map[p.toString()] = new EmptyCubeData(p.x, p.y, Config.empty_cube);
			}
		}
		
		private function resetMap():void{
			map = new Dictionary();
			for(var i:int = 0;i<Config.STAGE_HEIGHT*Config.STAGE_WIDTH;i++){
				var p:Point = new Point(i%10, Math.floor(i/10));
				map[p.toString()] = new EmptyCubeData(p.x, p.y, Config.empty_cube);
			}
		}
		
		private function preValidate():Boolean{
			var r:Boolean = true;
			for(var i:int = 0;i<shape.cubes.length;i++){
				if(!(map[shape.cubes[i].point.toString()] is EmptyCubeData)){
					if(shape.cubes[i].x < 0 || shape.cubes[i].x >= Config.STAGE_WIDTH){
						//beyound the side boundary
						r = false;
						break;
					}else{
						if(map[shape.cubes[i].point.toString()] is StandingCubeData || map[shape.cubes[i].point.toString()] is UnbreakableCubeData){
							r = false;
							break;
						}
					}
				}
			}
			return r;
		}
		
		private function validateLeft():Boolean{
			if(!preValidate()){
				return false;
			}
			var r:Boolean = true;
			for(var i:int = 0;i<shape.cubes.length;i++){
				var p:Point = shape.cubes[i].point;
				if(p.x <= 0 || !(map[new Point(p.x - 1, p.y).toString()] is EmptyCubeData)){
					//validate failed
					r = false;
					break;
				}
			}
			return r;
		}
		
		private function validateRight():Boolean{
			if(!preValidate()){
				return false;
			}
			var r:Boolean = true;
			for(var i:int = 0;i<shape.cubes.length;i++){
				var p:Point = shape.cubes[i].point;
				if(p.x >= Config.STAGE_WIDTH-1 || !(map[new Point(p.x + 1, p.y).toString()] is EmptyCubeData)){
					//validate failed
					r = false;
					break;
				}
			}
			return r;
		}
		
		private function validateDown():Boolean{
			if(!preValidate()){
				return false;
			}
			var r:Boolean = true;
			for(var i:int = 0;i<shape.cubes.length;i++){
				var p:Point = shape.cubes[i].point;
				if(p.y >= Config.STAGE_HEIGHT-1){
					r = false;
					break;
				}else{
					if(map[new Point(p.x, p.y + 1).toString()] != null){
						if(map[new Point(p.x, p.y + 1).toString()] is UnbreakableCubeData || map[new Point(p.x, p.y + 1).toString()] is StandingCubeData){
							//validate failed
							r = false;
							break;
						}
					}
				}
			}
			return r;
		}
		
		private function updateMap():void{
			for(var i:int = 0;i<shape.cubes.length;i++){
				var cube:CubeData = shape.cubes[i];
				var nc:StandingCubeData = new StandingCubeData(cube.x, cube.y, cube.skin);
				map[cube.point.toString()] = nc;
			}
			destroyShape();
			//redraw();
			dispatch(new MapEvent(MapEvent.MAP_UPDATE, null, null));
		}
		
		private function countdown():void{
			timer.addEventListener(Event.ENTER_FRAME, enterFrame);
			reset();
		}
		
		private function stopCountdown():void{
			timer.removeEventListener(Event.ENTER_FRAME, enterFrame);
			reset();
		}
		
		private function enterFrame(e:Event):void{
			if(shape == null){
				stopCountdown();
				return;
			}
			counter = now - startTime;
			if(counter >= Config.DROP_DELAY){
				//drop the shape from 1 step
				if(validateDown()){
					redraw(shape.stepDown);
					reset();
				}else{
					updateMap();
				}
			}
		}
		
		private function destroyShape():void{
			shape.dispose();
			shape = null;
		}
		
		private function redraw(func:Function):void{
			var was:IShape = shape.clone() as IShape;
			if(func != null){
				func();
			}
			dispatch(new P2PEvent(P2PEvent.SEND_MESSAGE, getDataToSend()));
			dispatch(new MapEvent(MapEvent.MAP_CHANGE, was, shape));
		}
		
		private function reset():void{
			counter = 0;
			startTime = now;
		}
		
		private function get now():int{
			return (new Date).time;
		}
	}
}